#include "OneButton.h"
#include "i2s_mic.h"
#include <Arduino.h>
#include <DNSServer.h>
#include <ESPmDNS.h> //用于设备域名 MDNS.begin("esp32")
#include <HardwareSerial.h>
#include <Preferences.h>
#include <String.h>
#include <WebServer.h>
#include <cmath>
#include <esp_wifi.h> //用于esp_wifi_restore() 删除保存的wifi信息
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "WiFiUdp.h"
#include <Update.h>
#include <WiFi.h>

#define MSG_LENGTH 30
typedef struct {
    int  len;
    char msg[MSG_LENGTH];
} qMsg;

#define PIN_ADC     35
#define PIN_LED_BAT 13
// ------------------------------------------------------------------------------------------
#define RESET_PIN  21 // need modify
#define PIN_MIC    14 // mic
#define LED_PIN    2
#define LED_ON     digitalWrite(LED_PIN, 1)
#define LED_OFF    digitalWrite(LED_PIN, 0)
#define LED_TOGGLE digitalWrite(LED_PIN, !digitalRead(LED_PIN))

void initLED()
{
    pinMode(LED_PIN, OUTPUT);
    pinMode(PIN_LED_BAT, OUTPUT);
    LED_OFF;
}
void blinkLED(int led, int n, int t)
{
    for (int i = 0; i < 2 * n; i++) {
        LED_TOGGLE;
        delay(t);
    }
}

int           debugPort = 8990;          // debug udp port
int           dnsPort = 53;              //设置DNS端口号
int           webServerPort = 80;        //设置Web端口号
IPAddress     apIP(192, 168, 4, 1);      //设置AP的IP地址
char*         apSSID = "AudioPen1-WiFi"; //设置AP热点名称
char*         apPSWD = "12345678";       //设置AP热点密码
int           connectTimeOut_s = 15;     // WiFi连接超时时间，单位秒
String        wifi_ssid = "";            //暂时存储wifi账号密码
String        wifi_pass = "";            //暂时存储wifi账号密码
String        serveraddr = "";
int           serverport = 8888; //默认8888
String        mserveraddr = "";
String        sn = "";
String        scanNetworksID = "";   //用于储存扫描到的WiFi ID
DNSServer     dnsServer;             //创建dnsServer实例
WebServer     server(webServerPort); //开启web服务, 创建TCP SERVER,参数: 端口号,最大连接数
WiFiClient    tcpClient;
TaskHandle_t  xTaskSend;
TaskHandle_t  xTaskBtn;
TaskHandle_t  xTaskLoop;
TaskHandle_t  xTaskRcvd;
TaskHandle_t  xTaskDebug;
WiFiUDP       udp;
WiFiUDP       udpDebug;
String        remoteUdpIPStr;        // 自定义远程监 IP 地址 = server ip
unsigned int  remoteUdpPort = 18300; // 自定义远程监听端口
uint32_t      lastSendTickSec = 0;
uint32_t      sendTickSec = 0;
volatile bool esp32wifiClosed = true;
volatile int  useUDPDebug = 0;
QueueHandle_t debugSendQueue = xQueueCreate(5, sizeof(qMsg));
#define UDP_BROADCAST_ADDR "192.168.31.255"
#define UDP_SERVER_ADDR    "192.168.31.67" //需要根据激光鼠标软件服务器的地址，中海192.168.31.67，成阿192.168.31.101
#define ROOT_HTML                                                                                                                                                                      \
    "<!DOCTYPE html><html><head><title>WIFI Config by YH-JackWang</title><meta "                                                                                                       \
    "name=\"viewport\" content=\"width=device-width, "                                                                                                                                 \
    "initial-scale=1\"></head><style type=\"text/css\">.input{display: block; "                                                                                                        \
    "margin-top: 10px;}.input span{width: 100px; float: left; float: left; "                                                                                                           \
    "height: 36px; line-height: 36px;}.input input{height: 30px;width: "                                                                                                               \
    "200px;}.btn{width: 120px; height: 35px; background-color: #000000; "                                                                                                              \
    "border:0px; color:#ffffff; margin-top:15px; "                                                                                                                                     \
    "margin-left:100px;}</style><body><form method=\"POST\" "                                                                                                                          \
    "action=\"configwifi\"><label class=\"input\"><span>WiFi SSID</span><input "                                                                                                       \
    "type=\"text\" name=\"ssid\"></label><label class=\"input\"><span>WiFi "                                                                                                           \
    "PASS</span><input type=\"text\"  name=\"pass\"></label><label class=\"input\"><span>Mouse Ip</span><input type=\"text\" name=\"mouseserver\" value=191.168.31.67></label><label " \
    "class=\"input\"><span>Audio IP</span><input type=\"text\" "                                                                                                                       \
    "name=\"serveraddr\" value=192.168.31.236></label><label "                                                                                                                         \
    "class=\"input\"><span>Audio Port</span><input type=\"text\" "                                                                                                                     \
    "name=\"serverport\" value=8888></label><input class=\"btn\" "                                                                                                                     \
    "type=\"submit\" name=\"submit\" value=\"Submit\"> <p><span> Nearby "                                                                                                              \
    "wifi:</P></form>"
#define SUCCESS_HTML                                                         \
    "<html><body><font size=\"10\">successd,wifi connecting...<br />Please " \
    "close this page manually.</font></body></html>"

String indexhtml = String("") + "<!DOCTYPE html>\n" + "<head>\n" + "    <meta charset=\"UTF-8\">\n" + "    <title>Update dev</title>\n" + "</head>\n" + "<body>\n" + "    <form method=\'POST\' action=\'/update\' enctype=\'multipart/form-data\'>\n" + "        <input type=\'file\' name=\'firmware\'>\n" + "        <input type=\'submit\'>\n" + "    </form>\n" + "</body>\n";
bool   shouldreboot = false; // 重启标志，固件升级操作完成后设置该标志准备重启设备

// ------------------------------------------------------------------------------------------
void initWiFi()
{
    WiFi.disconnect(true);
    WiFi.mode(WIFI_STA);
    WiFi.setSleep(true);
}
void initSoftAP()
{
    WiFi.mode(WIFI_AP);                                         //配置为AP模式
    WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); //设置AP热点IP和子网掩码
    if (WiFi.softAP(apSSID, apPSWD)) {                          //开启AP热点,如需要密码则添加第二个参数
        Serial.println("AudioPen SoftAP is right.");
        Serial.print("Soft-AP IP address = ");
        Serial.println(WiFi.softAPIP());
        Serial.println(String("MAC address = ") + WiFi.softAPmacAddress().c_str());
    } else { //开启热点失败
        Serial.println("WiFi AP Failed");
        delay(1000);
        Serial.println("restart now...");
        ESP.restart();
    }
}
void initDNS()
{
    //判断将所有地址映射到esp32的ip上是否成功
    if (dnsServer.start(dnsPort, "audiopen", apIP)) {
        Serial.println("start dnsserver success.");
    } else {
        Serial.println("start dnsserver failed.");
    }
}
void initWebServer()
{
    //给设备设定域名AudioPen,完整的域名是AudioPen.local
    if (MDNS.begin("AudioPen")) {
        Serial.println("MDNS responder started");
    }
    //必须添加第二个参数HTTP_GET，以下面这种格式去写，否则无法强制门户
    server.on("/", HTTP_GET, handleRoot);
    server.on("/configwifi", HTTP_POST, handleConfigWifi);
    server.onNotFound(handleNotFound); //当浏览器请求的网络资源无法在服务器找到时调用自定义函数handleNotFound处理
    server.begin();                    //启动 SERVER
                                       // server.setNoDelay(true); //关闭延时发送
    Serial.println("WebServer started!");
}
bool scanWiFi()
{
    Serial.println("wifi networks scan start");
    // 扫描附近WiFi
    int n = WiFi.scanNetworks(); //太多了就要五个
    Serial.println("wifi networks scan done");
    if (n == 0) {
        Serial.println("no networks found");
        scanNetworksID = "no networks found";
        return false;
    } else {
        Serial.print(n);
        Serial.println(" networks found");
        for (int i = 0; i < n; ++i) {
            // Print SSID and RSSI for each network found
            Serial.print(i + 1);
            Serial.print(": ");
            Serial.print(WiFi.SSID(i));
            Serial.print(" (");
            Serial.print(WiFi.RSSI(i));
            Serial.print(")");
            Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
            scanNetworksID += "<P>" + WiFi.SSID(i) + "</P>";
            delay(10);
        }
        return true;
    }
}
void wifiConnecting()
{
    WiFi.setAutoConnect(false); // 设置自动连接
    // wifi_ssid = "Mi 11";
    // wifi_pass = "abc222ly";
    WiFi.begin(wifi_ssid.c_str(), wifi_pass.c_str());
    int connectTime = 0;          //用于连接计时，如果长时间连接不成功，复位设备
    while (!WiFi.isConnected()) { //等待WIFI连接成功
        LED_TOGGLE;
        vTaskDelay(pdMS_TO_TICKS(50));
        if (connectTime++ > 5 * 20) {
            ESP.restart();
        }
    }
    Serial.println("WiFi connect success");
    Serial.print("local IP:");
    Serial.println(WiFi.localIP());
}
void wifiDisConnect()
{
    WiFi.disconnect(true);
    while (WiFi.isConnected()) {
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    Serial.println("WiFi disconnected");
}
void wifiWebConfig()
{
    // Serial.println("go to web wifi config page");
    initSoftAP();
    initDNS();
    initWebServer();
    scanWiFi();
    while (1) {
        dnsServer.processNextRequest(); //检查客户端DNS请求
        server.handleClient();          //检查客户端(浏览器)http请求
        delay(100);
        LED_TOGGLE;
    }
}
void handleRoot()
{
    server.send(200, "text/html", ROOT_HTML + scanNetworksID + "</body></html>");
}
void handleConfigWifi()
{
    if (server.hasArg("ssid")) {
        wifi_ssid = server.arg("ssid");
        debugSend("web config ssid: " + wifi_ssid + "\n");
    } else {
        debugSend("error, not found ssid");
        server.send(200, "text/html",
                    "<meta charset='UTF-8'>error, not found ssid"); //返回错误页面
        return;
    }

    if (server.hasArg("pass")) {
        wifi_pass = server.arg("pass");
        debugSend("web config password: " + wifi_pass + "\n");
    } else {
        debugSend("error, not found password");
        server.send(200, "text/html", "<meta charset='UTF-8'>error, not found password");
        return;
    }

    if (server.hasArg("serveraddr")) {
        serveraddr = server.arg("serveraddr");
        debugSend("web config serveraddr: " + serveraddr + "\n");
    }

    if (server.hasArg("serverport")) {
        serverport = server.arg("serverport").toInt();
        debugSend("web config serverport: " + String(serverport) + "\n");
    }

    if (server.hasArg("mouseserver")) {
        mserveraddr = server.arg("mouseserver");
        debugSend("web config serveraddr: " + mserveraddr + "\n");
    }

    // save to eeprom
    Preferences prefs;
    prefs.begin("wifiConfig");
    prefs.putString("ssid", wifi_ssid);
    prefs.putString("pswd", wifi_pass);
    prefs.putString("serveraddr", serveraddr);
    prefs.putString("mserveraddr", mserveraddr);
    prefs.putInt("serverport", serverport);
    prefs.end();

    server.send(200, "text/html",
                "<meta charset='UTF-8'>SSID：" + wifi_ssid + "<br />password:" + wifi_pass + "<br />配置WiFi信息成功,设备重启中..."); //返回保存成功页面
    delay(200);

    WiFi.softAPdisconnect(
        true);               //参数设置为true，设备将直接关闭接入点模式，即关闭设备所建立的WiFi网络。
    server.close();          //关闭web服务
    WiFi.softAPdisconnect(); //在不输入参数的情况下调用该函数,将关闭接入点模式,并将当前配置的AP热点网络名和密码设置为空值.
    Serial.println("WiFi Connect SSID:" + wifi_ssid + "  PASS:" + wifi_pass);
    Serial.println("reboot");
    delay(100);
    ESP.restart();
}

void handleWebConfig()
{
    if (server.hasArg("ssid")) {
        wifi_ssid = server.arg("ssid");
        debugSend("web config ssid: " + wifi_ssid + "\n");
        Serial.println("web config ssid: " + wifi_ssid + "\n");
    } else {
        server.send(200, "text/html", "<meta charset='UTF-8'>error, not found ssid"); //返回错误页面
        debugSend("error, not found ssid");
        Serial.println("error, not found ssid");
        return;
    }

    if (server.hasArg("pass")) {
        wifi_pass = server.arg("pass");
        debugSend("web config password: " + wifi_pass + "\n");
        Serial.println("web config password: " + wifi_pass + "\n");
    } else {
        debugSend("error, not found password\n");
        Serial.println("error, not found password\n");
        server.send(200, "text/html", "<meta charset='UTF-8'>error, not found password");
        return;
    }

    if (server.hasArg("serveraddr")) {
        serveraddr = server.arg("serveraddr");
        debugSend("web config serveraddr: " + serveraddr + "\n");
        Serial.println("web config serveraddr: " + serveraddr + "\n");
    }

    if (server.hasArg("serverport")) {
        serverport = server.arg("serverport").toInt();
        debugSend("web config serverport: " + String(serverport) + "\n");
        Serial.println("web config serverport: " + String(serverport) + "\n");
    }

    if (server.hasArg("mouseserver")) {
        mserveraddr = server.arg("mouseserver");
        debugSend("web config mserveraddr: " + mserveraddr + "\n");
        Serial.println("web config mserveraddr: " + mserveraddr + "\n");
    }

    // save to eeprom
    Preferences prefs;
    prefs.begin("wifiConfig");
    prefs.putString("ssid", wifi_ssid);
    prefs.putString("pswd", wifi_pass);
    prefs.putString("serveraddr", serveraddr);
    prefs.putString("mserveraddr", mserveraddr);
    prefs.putInt("serverport", serverport);
    prefs.end();

    server.send(200, "text/html", "<meta charset='UTF-8'>SSID：" + wifi_ssid + "<br />password:" + wifi_pass + "<br />配置WiFi信息成功,设备重启中..."); //返回保存成功页面
    delay(1000);
    delay(1000);
    ESP.restart();
}
void handleNotFound()
{
    handleRoot();
}
void handleResponse_ota() //回调函数
{
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", (Update.hasError()) ? "FAIL" : "OK");
}
void handleIp() //回调函数
{
    server.send(200, "text/plane", WiFi.localIP().toString());
}
void handleShowConfig() //回调函数
{
    server.send(200, "text/html", ROOT_HTML + scanNetworksID + "</body></html>");
}

void handleRoot_ota() //回调函数
{
    String username = "dev" + sn;
    String userpassword = sn;
    if (!server.authenticate(username.c_str(), userpassword.c_str())) //校验用户是否登录
    {
        return server.requestAuthentication(); //请求进行用户登录认证
    }
    server.sendHeader("Connection", "close");
    server.send(200, "text/html", indexhtml); //登录成功则显示真正的内容
}
void handleFileupload_ota() //回调函数
{
    HTTPUpload& upload = server.upload();   // 文件上传对象
    if (upload.status == UPLOAD_FILE_START) // 文件上传开始
    {
        debugSend("start upload\n");
        if (!Update.begin()) // 开始固件升级，检查空可用间大小，如果正在升级或空间不够则返回false
        {
            Update.printError(Serial);
        }
    } else if (upload.status == UPLOAD_FILE_WRITE) // 文件读写中
    {
        if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) // 将文件数据写入到OTA区域
        {
            Update.printError(Serial);
        }
    } else if (upload.status == UPLOAD_FILE_END) // 文件上传完成
    {
        debugSend("write " + String(upload.totalSize) + "B to OTA\n");
        if (!Update.end(true)) // 完成数据写入，设置在系统重启后自动将OTA区域固件移动到Sketch区域 // Update.begin不指定大小时这里设置true
        {
            Update.printError(Serial);
        }
        debugSend("finished, reboot after 1s\n");
        shouldreboot = true;
    } else {
        debugSend("upload error = " + String(upload.status) + "\n");
    }
}
void wifiRestore()
{
    Serial.print("clear wifi info...");
    Preferences prefs;
    prefs.begin("wifiConfig");
    prefs.remove("ssid");
    prefs.remove("pswd");
    prefs.end();
    blinkLED(LED_PIN, 6, 100); // LED闪烁5次
    LED_OFF;
    wifi_ssid = "";
    wifi_pass = "";
    LED_ON;
    delay(3000);
    ESP.restart();
}
bool isWiFiConfigValid()
{
    Preferences prefs;
    prefs.begin("wifiConfig");
    if (prefs.isKey("ssid") && prefs.isKey("pswd") && prefs.isKey("serveraddr") && prefs.isKey("serverport") && prefs.isKey("mserveraddr")) {
        Serial.printf("config ssid read: %s\r\n", prefs.getString("ssid"));
        Serial.printf("config pswd read: %s\r\n", prefs.getString("pswd"));
        Serial.printf("config mserveraddr read: %s\r\n",
                      prefs.getString("mserveraddr"));
        Serial.printf("config serveraddr read: %s\r\n",
                      prefs.getString("serveraddr"));
        Serial.printf("config serverport read: %d\r\n", prefs.getInt("serverport"));
        wifi_ssid = prefs.getString("ssid");
        wifi_pass = prefs.getString("pswd");
        serveraddr = prefs.getString("serveraddr");
        serverport = prefs.getInt("serverport");
        mserveraddr = prefs.getString("mserveraddr");
    } else {
        wifi_ssid = "";
        wifi_pass = "";
    }
    prefs.end();

    if (wifi_ssid != "" && wifi_pass != "" && serveraddr != "" && mserveraddr != "" && serverport != 0) {
        return true;
    } else {
        return false;
    }
}
bool snConfigCheck()
{
    Preferences prefs;
    prefs.begin("snConfig");
    if (prefs.isKey("sn")) {
        Serial.printf("config sn read: %s\r\n", prefs.getString("sn"));
        sn = prefs.getString("sn");
    } else {
        sn = "";
    }
    prefs.end();

    if (sn != "") {
        return true;
    } else {
        return false;
    }
}
void wifiTryConn()
{
    if (!WiFi.isConnected()) {
        initWiFi();
        if (!digitalRead(RESET_PIN)) {
            wifiWebConfig(); //转到网页端手动配置wifi
        }
        if (!isWiFiConfigValid()) {
            wifiWebConfig(); //转到网页端手动配置wifi
        }
        wifiConnecting();
    }
}
void udpSend(String msg)
{
    wifiTryConn();
    Serial.println(msg);
    // udp.beginPacket(serveraddr.c_str(), remoteUdpPort);
    if (mserveraddr != "") {
        udp.beginPacket(mserveraddr.c_str(), remoteUdpPort);
    } else {
        udp.beginPacket(UDP_SERVER_ADDR, remoteUdpPort);
    }

    udp.print(msg);
    udp.endPacket();
    lastSendTickSec = sendTickSec;
}
void debugSend(String msg)
{
    qMsg sendMsg;
    int  len = msg.length();
    if (len < MSG_LENGTH) {
        memset(&sendMsg, 0, sizeof(sendMsg));
        sendMsg.len = len;
        memcpy(sendMsg.msg, msg.c_str(), len);
        xQueueSend(debugSendQueue, &sendMsg, portMAX_DELAY);
    }
}
// ------------------------------------------------------------------------------------------
#define CMD_LEFT_CLICK         "btn:left_clicked\r\n"
#define CMD_LEFT_DOUBLE_CLICK  "btn:left_double_clicked\r\n"
#define CMD_LEFT_LONG_CLICK    "btn:left_long_clicked\r\n"
#define CMD_LEFT_LONG_RELEASE  "btn:left_long_clicked_release\r\n"
#define CMD_RIGHT_CLICK        "btn:right_clicked\r\n"
#define CMD_RIGHT_DOUBLE_CLICK "btn:right_double_clicked\r\n"
#define CMD_RIGHT_LONG_CLICK   "btn:right_long_clicked\r\n"
#define CMD_RIGHT_LONG_RELEASE "btn:right_long_clicked_release\r\n"
#define CMD_PAGE_UP_CLICK      "btn:page_up_clicked\r\n"
#define CMD_PAGE_DOWN_CLICK    "btn:page_down_clicked\r\n"
#define CMD_SWITCH_CLICK       "btn:switch_clicked\r\n"
#define CMD_AUDIO_CLICK        "btn:audio_clicked\r\n"
#define CMD_USR_CLICK          "btn:usr_clicked\r\n"
// click btn
#define PIN_PAGE_DOWN 5
#define PIN_PAGE_UP   18
#define PIN_SWITCH    19
#define PIN_LEFT      16
#define PIN_RIGHT     22
#define PIN_AUDIO     4
#define PIN_USR       17
OneButton btnPageDown(PIN_PAGE_DOWN);
OneButton btnPageUp(PIN_PAGE_UP);
OneButton btnSwitch(PIN_SWITCH);
OneButton btnLeft(PIN_LEFT);
OneButton btnRight(PIN_RIGHT);
OneButton btnAudio(PIN_AUDIO);
OneButton btnMic(PIN_MIC);
OneButton btnUsr(PIN_USR);
bool      isBtnMicValid = false;
void      initPushBTN()
{
    btnMic.reset();
    btnMic.attachLongPressStart(btnMiclongPressStart);
    btnMic.attachLongPressStop(btnMiclongPressStop);
    btnMic.setDebounceTicks(50); //忽略其他级别更改的时间段
    btnMic.setClickTicks(100);   //用于区分单次单击和双击的超时
    btnMic.setPressTicks(300);   //按住按钮以触发长按的持续时间

    btnLeft.reset();
    btnLeft.attachClick(btnLeftClick);
    // btnLeft.attachDoubleClick(btnLeftDoubleClick);
    btnLeft.attachLongPressStart(btnLeftlongPressStart);
    btnLeft.attachLongPressStop(btnLeftlongPressStop);
    btnLeft.setDebounceTicks(30); //忽略其他级别更改的时间段
    btnLeft.setClickTicks(100);   //用于区分单次单击和双击的超时
    btnLeft.setPressTicks(200);   //按住按钮以触发长按的持续时间

    btnRight.reset();
    btnRight.attachClick(btnRightClick);
    // btnRight.attachDoubleClick(btnRightDoubleClick);
    btnRight.attachLongPressStart(btnRightlongPressStart);
    btnRight.attachLongPressStop(btnRightlongPressStop);
    btnRight.setDebounceTicks(30); //忽略其他级别更改的时间段
    btnRight.setClickTicks(100);   //用于区分单次单击和双击的超时
    btnRight.setPressTicks(200);   //按住按钮以触发长按的持续时间

    btnPageUp.reset();
    btnPageUp.attachClick(btnPageUpClick);
    btnPageUp.attachDuringLongPress(btnPageUpDuringLongPress);
    btnPageUp.setPressTicks(200); //按住按钮以触发长按的持续时间

    btnPageDown.reset();
    btnPageDown.attachClick(btnPageDownClick);
    btnPageDown.attachDuringLongPress(btnPageDownDuringLongPress);
    btnPageDown.setPressTicks(200); //按住按钮以触发长按的持续时间

    btnSwitch.reset();
    btnSwitch.attachLongPressStart(btnSwitchLongPressStart);
    btnSwitch.setPressTicks(300); //按住按钮以触发长按的持续时间

    btnUsr.reset();
    btnUsr.attachLongPressStart(btnUsrLongPressStart);
    btnUsr.setPressTicks(2000); //按住按钮以触发长按的持续时间

    btnAudio.reset();
    btnAudio.attachClick(btnAudioClick);

    pinMode(RESET_PIN, INPUT_PULLUP);
}
void btnMiclongPressStart()
{
    isBtnMicValid = true;
    debugSend("btnMiclongPressStart\n");
}
void btnMiclongPressStop()
{
    btnMic.reset();
    isBtnMicValid = false;
    debugSend("btnMiclongPressStop\n");
}
void btnLeftClick()
{
    udpSend(CMD_LEFT_CLICK);
    debugSend("btnLeftClick\n");
}
void btnLeftDoubleClick()
{
    udpSend(CMD_LEFT_DOUBLE_CLICK);
    debugSend("btnLeftDoubleClick\n");
}
void btnLeftlongPressStart()
{
    udpSend(CMD_LEFT_LONG_CLICK);
    debugSend("btnLeftlongPressStart\n");
}
void btnLeftlongPressStop()
{
    udpSend(CMD_LEFT_LONG_RELEASE);
    btnLeft.reset();
    debugSend("btnLeftlongPressStop\n");
}
void btnRightClick()
{
    udpSend(CMD_RIGHT_CLICK);
    debugSend("btnRightClick\n");
}
void btnRightDoubleClick()
{
    udpSend(CMD_RIGHT_DOUBLE_CLICK);
    debugSend("btnRightDoubleClick\n");
}
void btnRightlongPressStart()
{
    udpSend(CMD_RIGHT_LONG_CLICK);
    debugSend("btnRightlongPressStart\n");
}
void btnRightlongPressStop()
{
    udpSend(CMD_RIGHT_LONG_RELEASE);
    btnRight.reset();
    debugSend("btnRightlongPressStop\n");
}
void btnPageUpClick()
{
    udpSend(CMD_PAGE_UP_CLICK);
    debugSend("btnPageUpClick\n");
}
void btnPageUpDuringLongPress()
{
    if (btnPageUp.isLongPressed()) {
        udpSend(CMD_PAGE_UP_CLICK);
        debugSend("btnPageUpDuringLongPress\n");
        delay(100);
    }
}
void btnPageDownClick()
{
    udpSend(CMD_PAGE_DOWN_CLICK);
    debugSend("btnPageDownClick\n");
}
void btnPageDownDuringLongPress()
{
    if (btnPageDown.isLongPressed()) {
        udpSend(CMD_PAGE_DOWN_CLICK);
        debugSend("btnPageDownDuringLongPress\n");
        delay(100);
    }
}
void btnSwitchLongPressStart()
{
    udpSend(CMD_SWITCH_CLICK);
    debugSend("btnSwitchLongPressStart\n");
}
void btnUsrLongPressStart()
{
    udpSend(CMD_USR_CLICK);
    useUDPDebug = useUDPDebug ? 0 : 1;
    debugSend("btnUsrLongPressStart\n");
}
void btnAudioClick()
{
    udpSend(CMD_AUDIO_CLICK);
    debugSend("btnAudioClick\n");
}
// ------------------------------------------------------------------------------------------
float          samples[SAMPLE_COUNT];
uint16_t       samples_16bit[SAMPLE_COUNT];
Microphone_I2S mic;
volatile bool  isMicRunning = false;
QueueHandle_t  sendQueue = xQueueCreate(2, sizeof(samples_16bit));
void           startMicSampling()
{
    if (!isMicRunning) {
        wifiTryConn();
        if (!tcpClient.connected()) {
            if (tcpClient.connect(serveraddr.c_str(), serverport)) {
                debugSend("tcp server connected\n");
                mic.startSampling();
                debugSend("start sampling from micn\n");
                isMicRunning = true;
                LED_ON;
            }
        } else {
            mic.startSampling();
            debugSend("start sampling from mic\n");
            isMicRunning = true;
            LED_ON;
        }
    }
}
uint8_t stopCmd[] = { 's', 't', 'o', 'p', 'm', 'i', 'c', '\r', '\n' };
void    stopMicSampling()
{
    if (isMicRunning) {
        mic.stopSampling();
        LED_OFF;
        debugSend("stop sampling from mic\n");
        isMicRunning = false;
    }
}
// ------------------------------------------------------------------------------------------
void setup()
{
    Serial.begin(115200);
    Serial.println("\r\n\r\n\r\n SerialRunning setup");

    initLED();
    initPushBTN();

    if (0) {
        Preferences prefs;
        prefs.begin("snConfig");
        prefs.putString("sn", "230112012");
        prefs.end();
    }

    if (0) {
        // save to eeprom
        Preferences prefs;
        prefs.begin("wifiConfig");
        prefs.putString("ssid", "Xiaomi_20A5");
        prefs.putString("pswd", "hwzh1234");
        prefs.putString("mserveraddr", "192.168.31.67");
        prefs.putString("serveraddr", "192.168.31.236");
        prefs.putInt("serverport", 8888);
        prefs.end();
    }

    if (!snConfigCheck()) {
        while (1) {
            delay(300);
            digitalWrite(PIN_LED_BAT, 1);
            delay(300);
            digitalWrite(PIN_LED_BAT, 0);
        }
    }

    WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
    String hostName = "dev" + sn;
    if (!WiFi.setHostname(hostName.c_str())) {
        Serial.println("Hostname failed to configure");
    }
    if (!MDNS.begin(hostName.c_str())) { // http://dev_sn.local
        Serial.println("Error setting up MDNS responder!");
        while (1) {
            delay(1000);
        }
    }

    wifiTryConn();
    server.on("/", handleRoot_ota);
    server.on("/ip", handleIp);
    server.on("/config", handleShowConfig);
    server.on("/configwifi", HTTP_POST, handleWebConfig);
    server.on("/update", HTTP_POST, handleResponse_ota, handleFileupload_ota); // 绑定回调函数
    server.begin();                                                            //启动服务器

    mic.begin();
    xTaskCreatePinnedToCore(sendAudioTask, "audiotask", 10000, NULL, 5, &xTaskSend, 0);
    xTaskCreatePinnedToCore(rcvdAudioTask, "audioRxtask", 10000, NULL, 6, &xTaskRcvd, 1);
    xTaskCreatePinnedToCore(pushBtnCheckTask, "btnChecktask", 10000, NULL, 4, &xTaskBtn, 0);
    xTaskCreatePinnedToCore(btnTickTask, "btnLooptask", 10000, NULL, 3, &xTaskLoop, 1);
    xTaskCreatePinnedToCore(debugTask, "debugTask", 10000, NULL, 6, &xTaskDebug, 0);
    xQueueReset(sendQueue);
    LED_OFF;
    Serial.println("setup done");
}

void debugTask(void* params)
{
    Serial.println("debugTask running");
    qMsg rcvdMsg;
    while (true) {
        memset(&rcvdMsg, 0, sizeof(rcvdMsg));
        if (xQueueReceive(debugSendQueue, &rcvdMsg, portMAX_DELAY)) {
            wifiTryConn();
            udpDebug.beginPacket(UDP_BROADCAST_ADDR, debugPort);
            udpDebug.print(sn + ":" + String(rcvdMsg.msg));
            udpDebug.endPacket();
            // delay(5);
        }
    }
}

void sendAudioTask(void* params)
{
    Serial.println("sendAudioTask running");
    int      lastLoopTime;
    uint16_t samples_tcp[SAMPLE_COUNT];
    while (true) {
        while (xQueueReceive(sendQueue, &samples_tcp, portMAX_DELAY)) {
            if (!isMicRunning) {
                delay(10);
                tcpClient.write((uint8_t*)&stopCmd, sizeof(stopCmd));
                xQueueReset(sendQueue);
                debugSend("stopMic\n");
            } else {
                tcpClient.write((uint8_t*)&samples_tcp, sizeof(samples_tcp));
                auto currentLoopTime = millis();
                debugSend(String(currentLoopTime - lastLoopTime) + " ms\n");
                lastLoopTime = currentLoopTime;
            }
        }
    }
}

void rcvdAudioTask(void* params)
{
    Serial.println("sendAudioTask running");
    float ratio = 0.0f;
    float enh = 8;
    int   lastLoopTime;
    while (true) {
        while (isMicRunning && xQueueReceive(mic.sampleQueue(), &samples, portMAX_DELAY)) {
            // i2s 24bit ->  216bit
            for (int k = 0; k < SAMPLE_COUNT; k++) {
                ratio = samples[k] / 16777216;
                samples_16bit[k] = (uint16_t)(enh * ratio * 32767);
            }
            // send to tcp task
            xQueueSend(sendQueue, &samples_16bit, portMAX_DELAY);
            delay(1);
        }
        // xQueueReset(mic.sampleQueue());
        delay(100);
    }
}

void pushBtnCheckTask(void* params)
{
    Serial.println("btnCheckTask running");
    while (true) {
        if (isBtnMicValid) {
            // 按键连接wifi和音频采集
            startMicSampling();
        } else {
            stopMicSampling();
        }
        delay(100);
    }
}

void btnTickTask(void* params)
{
    Serial.println("btnTickTask running");
    while (true) {
        btnMic.tick();
        btnLeft.tick();
        btnRight.tick();
        btnPageUp.tick();
        btnPageDown.tick();
        btnSwitch.tick();
        btnAudio.tick();
        delay(1);

        server.handleClient(); //处理来自客户端的请求

        if (shouldreboot) {
            delay(1000);
            ESP.restart(); // 重启设备
        }
    }
}

int   adcValue = 0;
float batValue = 0;
int   isStartd = 0;
void  loop()
{
    if (isStartd == 0) {
        isStartd = 1;
        debugSend("build:" + String(__DATE__) + String(__TIME__));
    }
    delay(3000);
    // sendTickSec++;
    // if (sendTickSec - lastSendTickSec > 60) {
    //     lastSendTickSec = 0;
    //     sendTickSec = 0;
    //     tcpClient.stop();
    //     WiFi.disconnect(true);
    //     esp32wifiClosed = true;
    // }

    adcValue = analogRead(PIN_ADC);
    batValue = 4 * adcValue / 2045.0f;
    debugSend("volt " + String(batValue) + "v\r\n");

    if (batValue < 3.5) {
        digitalWrite(PIN_LED_BAT, 1);
    } else {
        digitalWrite(PIN_LED_BAT, 0);
    }

    // udpSend("test\r\n");
}